﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Jacobi.Vst.Core;
using Jacobi.Vst.Framework;
using Aleator.Resources;

namespace Aleator.Core
{
    /// <summary>
    /// Note class containing musical and MIDI properties and methods
    /// </summary>
    public class ConcreteNote : Note
    {
        #region Members

        private CircleOfFifths.ChromaticScaleNote m_chromaticNote;

        private int m_noteID;

        private string m_chordInterval;
        private string m_name;

        private int m_interval;
        private int m_octave;
        private int m_accidental;
        private int m_location;
        private int m_offLocation;
        private int m_measure;
        private int m_offMeasure;

        private string m_chord;
        private int m_gap;

        private ConcretePhrase m_phrase;
        private Duration m_noteDuration;
        private NoteType m_type;

        private int m_messageOnID;
        private int m_messageOffID;

        private byte[] m_midiData;
        private byte[] m_midiOffData;

        private static byte[] m_metronomeMeasureOnData = new byte[4] { 151, 60, 110, 0 };
        private static byte[] m_metronomeQuarterOnData = new byte[4] { 151, 60, 80, 0 };
        private static byte[] m_metronomeOffData = new byte[4] { 135, 60, 100, 0 };

        private static byte[] m_crashOnData = new byte[4] { 147, 60, 100, 0 };
        private static byte[] m_crashOffData = new byte[4] { 131, 60, 100, 0 };

        //Panic buttons
        private static byte[] m_bassNotesOff = new byte[4] { 179, 123, 0, 0 };
        private static byte[] m_melodyNotesOff = new byte[4] { 180, 123, 0, 0 };
        private static byte[] m_rhythmNotesOff = new byte[4] { 181, 123, 0, 0 };

        //Control temp in Reaper
        private static byte[] m_decreaseTempo = new byte[4] { 176, 14, 0, 0 };
        private static byte[] m_inceaseTempo = new byte[4] { 176, 15, 0, 0 };

        //Scrolling Channel 1
        private static byte[] m_scrollUp = new byte[4] { Byte.Parse(AppResources.CCOnMessageID), 0, 100, 0 };
        private static byte[] m_scrollDown = new byte[4] { Byte.Parse(AppResources.CCOnMessageID), 127, 100, 0 };

        //Scrolling Channel 7
        //private static byte[] m_scrollUp = new byte[4] { Byte.Parse(AppResources.ArpOnMessageID), 0, 0, 0 };
        //private static byte[] m_scrollDown = new byte[4] { Byte.Parse(AppResources.ArpOnMessageID), 127, 0, 0 };

        #endregion

        #region Constructors

        /// <summary>
        /// Default note constructor
        /// </summary>
        public ConcreteNote()
        {
        }
        
        /// <summary>
        /// Base note constructor
        /// </summary>
        /// <param name="note">Chromatic scale note value</param>
        /// <param name="notename">Name representation of the nore</param>
        /// <param name="noteID">MIDI keyboard number</param>
        /// <param name="interval">Step number within the scale</param>
        /// <param name="octave">Octave the note is to be played in</param>
        /// <param name="accidental">Indicator of sharp, flat or natural adjustment</param>
        public ConcreteNote(CircleOfFifths.ChromaticScaleNote note, string notename, int noteID, int interval, int octave, int accidental = 0)
        {
            m_chromaticNote = note;
            m_name = notename;
            m_noteID = noteID;
            m_interval = interval;
            m_octave = octave;

            if (accidental != 0)
                m_accidental = accidental;
        }

        /// <summary>
        /// Regular phrase note constructor
        /// </summary>
        /// <param name="location">Quantized location within the measure</param>
        /// <param name="measure">Which measure note it associated with</param>
        /// <param name="note">Chromatic scale note value</param>
        /// <param name="messageOnID">MIDI noteon id for this note's channel</param>
        /// <param name="messageOffID">MIDI noteon id for the this note's channel</param>
        /// <param name="phrase">Phrase that the note is associated with</param>
        /// <param name="chord">Chord the note is associated with</param>
        public ConcreteNote(int location, int measure, ConcreteNote note, int messageOnID, int messageOffID, ConcretePhrase phrase, string chord = null)
        {
            m_type = NoteType.Drum;

            m_location = location;
            m_measure = measure;
            m_messageOnID = messageOnID;
            m_messageOffID = messageOffID;
            m_chordInterval = phrase.ChordInterval;
            m_chord = chord;

            if (messageOnID.ToString() == AppResources.BassOnMessageID)
                m_type = NoteType.Bass;
            else if (messageOnID.ToString() == AppResources.LeadOnMessageID)
                m_type = NoteType.Lead;
            else if (messageOnID.ToString() == AppResources.RhythmOnMessageID)
                m_type = NoteType.Rhythm;
            else if (messageOnID.ToString() == AppResources.ArpOnMessageID)
                m_type = NoteType.Arpeggio;
            else if (messageOnID.ToString() == AppResources.UtilityOnMessageID)
                m_type = NoteType.Utility;
            else
                m_type = NoteType.Drum;

            m_noteID = note.m_noteID;
            m_name = note.m_name;
            m_octave = note.m_octave;

            m_midiData = new byte[4];
            m_midiData[0] = (byte)messageOnID;
            m_midiData[1] = (byte)m_noteID;
            m_midiData[2] = (byte)100;
            m_midiData[3] = 0;

            m_midiOffData = new byte[4];
            m_midiOffData[0] = (byte)messageOffID;
            m_midiOffData[1] = (byte)m_noteID;
            m_midiOffData[2] = (byte)0;
            m_midiOffData[3] = 0;

            if (m_phrase == null)
                m_phrase = phrase;

            NoteOnEvent = new VstMidiEvent(0, 0, 0, m_midiData, 0, 0);
            NoteOffEvent = new VstMidiEvent(0, 0, 0, m_midiOffData, 0, 0);
        }

        /// <summary>
        /// Drum note constructor
        /// </summary>
        /// <param name="location">Quantized location within the measure</param>
        /// <param name="measure">Which measure note it associated with</param>
        /// <param name="noteid">MIDI keyboard number</param>
        /// <param name="messageOnID">MIDI noteon id for this note's channel</param>
        /// <param name="messageOffID">MIDI noteon id for the this note's channel</param>
        /// <param name="phrase">Phrase that the note is associated with</param>
        /// <param name="chord">Chord the note is associated with</param>
        public ConcreteNote(int location, int measure, int noteid, int messageOnID, int messageOffID, ConcretePhrase phrase, string chord = null)
        {
            m_type = NoteType.Drum;

            m_location = location;
            m_measure = measure;
            m_messageOnID = messageOnID;
            m_messageOffID = messageOffID;
            m_chord = chord;

            m_noteID = noteid;

            m_midiData = new byte[4];
            m_midiData[0] = (byte)messageOnID;
            m_midiData[1] = (byte)m_noteID;
            m_midiData[2] = (byte)100;
            m_midiData[3] = 0;

            m_midiOffData = new byte[4];
            m_midiOffData[0] = (byte)messageOffID;
            m_midiOffData[1] = (byte)m_noteID;
            m_midiOffData[2] = (byte)0;
            m_midiOffData[3] = 0;

            if (m_phrase == null)
                m_phrase = phrase;

            NoteOnEvent = new VstMidiEvent(0, 128, 0, m_midiData, 0, 0);
            NoteOffEvent = new VstMidiEvent(0, 0, 0, m_midiOffData, 0, 0);
        }

        #endregion

        #region Properties

        /// <summary>
        /// MIDI note ID
        /// </summary>
        public override int NoteID
        {
            get
            {
                return m_noteID;
            }
            set
            {
                m_noteID = value;
            }
        }

        /// <summary>
        /// Name of the note
        /// </summary>
        public override string Name
        {
            get
            {
                return m_name;
            }
        }

        /// <summary>
        /// This note's interval within it's chord
        /// </summary>
        public override string ChordInterval
        {
            get
            {
                return m_chordInterval;
            }
            set
            {
                m_chordInterval = value;
            }
        }

        /// <summary>
        /// The scale degree of the note
        /// </summary>
        public override int Interval
        {
            get
            {
                return m_interval;
            }
        }

        /// <summary>
        /// This note's octave
        /// </summary>
        public override int Octave
        {
            get
            {
                return m_octave;
            }
        }

        /// <summary>
        /// String representation of octave number
        /// </summary>
        [Bindable(true)]
        public override string OctaveStr
        {
            get
            {
                return m_octave.ToString();
            }
        }

        /// <summary>
        /// Number of major ticks (
        /// </summary>
        public override int Resolution
        {
            get
            {
                if (Type == NoteType.Drum)
                {
                    if (m_noteID.ToString() == AppResources.KickNoteNumber)
                        return ((DrumPhrase)m_phrase).KickResolution;
                    else if (m_noteID.ToString() == AppResources.SnareNoteNumber || (m_noteID.ToString() == AppResources.SideStickNoteNumber) || (m_noteID.ToString() == AppResources.UtilityOnMessageID))
                        return ((DrumPhrase)m_phrase).SnareResolution;
                    else if (m_noteID.ToString() == AppResources.HiHatNoteNumber)
                        return ((DrumPhrase)m_phrase).HiHatResolution;
                    else
                        return 0;
                }
                else
                    return m_phrase.Resolution;
            }
        }

        /// <summary>
        /// This notes quantized location in the measure
        /// </summary>
        public override int Location
        {
            get
            {
                return m_location;
            }
            set
            {
                if (value <= Session.BeatsInMeasure)
                    m_location = value;
                else
                    Debug.WriteLine("Beat number out of range!");
            }
        }

        /// <summary>
        /// Integer indicating when the note should be turned off
        /// </summary>
        public override int OffLocation
        {
            get
            {
                return m_offLocation;
            }
            set
            {
                m_offLocation = value;
            }
        }

        /// <summary>
        /// Measure in which the on note is sent
        /// </summary>
        public override int Measure
        {
            get
            {
                return m_measure;
            }
            set
            {
                m_measure = value;

                if (value > m_phrase.Progression.TotalMeasures)
                    Debug.WriteLine(string.Format("Measure {0} is greater than original phrase length {1}. Note being copied or invalid measure.",
                        value, m_phrase.Progression.TotalMeasures));
            }
        }

        /// <summary>
        /// Measure during which the off note is sent
        /// </summary>
        public override int OffMeasure
        {
            get
            {
                return m_offMeasure;
            }
            set
            {
                m_offMeasure = value;
            }
        }

        /// <summary>
        /// How many major ticks result in one 'location' increment for the current phrase
        /// </summary>
        public override int MajorTickMultiplier
        {
            get
            {
                return Session.SessionResolution / Resolution;
            }
        }

        /// <summary>
        /// Name of the chord this note instance is part of
        /// </summary>
        public override string Chord
        {
            get
            {
                return m_chord;
            }
        }

        /// <summary>
        /// Integer reprenting the number of quantized note counts until the next note
        /// </summary>
        public override int Gap
        {
            get
            {
                return m_gap;
            }
            set
            {
                m_gap = value;
            }
        }

        /// <summary>
        /// The type of the note
        /// </summary>
        public override NoteType Type
        {
            get
            {
                return m_type;
            }
            set
            {
                m_type = value;
            }
        }

        /// <summary>
        /// Single string with some information about the note
        /// </summary>
        [Bindable(true)]
        public override string NoteInfo
        {
            get
            {
                string inst = "";

                if (m_type == NoteType.Drum)
                {
                    if (m_noteID.ToString() == AppResources.KickNoteNumber)
                        inst = "Kick";
                    if (m_noteID.ToString() == AppResources.SnareNoteNumber)
                        inst = "Snare";
                    if (m_noteID.ToString() == AppResources.SideStickNoteNumber)
                        inst = "Sidestick";
                    if (m_noteID.ToString() == AppResources.HiHatNoteNumber)
                        inst = "High Hat";
                }
                else
                    inst = Type.ToString();

                return string.Format("{0}: Measure {1}, Location {2}, OffMeasure {3}, OffLocation {4}", inst, m_measure, m_location, m_offMeasure, m_offLocation);
            }
            set
            {
                //Do nothing
            }
        }

        /// <summary>
        /// Fractional representation of the notes duration - 32nd, 16th, 8th, quarter, etc.
        /// </summary>
        public override Duration NoteDuration
        {
            get
            {
                if (m_noteDuration == Duration.Unspecified)
                    CalculateDuration();

                return m_noteDuration;
            }
        }

        /// <summary>
        /// String representation of duration
        /// </summary>
        [Bindable(true)]
        public override string DurationStr
        {
            get
            {
                return NoteDuration.ToString();
            }
        }

        public CircleOfFifths.ChromaticScaleNote ChromaticNote
        {
            get
            {
                return m_chromaticNote;
            }
        }

        #endregion

        #region MIDI properties

        /// <summary>
        /// NoteOn message for this note's MIDI channel
        /// </summary>
        public override int MessageOnID
        {
            get
            {
                return m_messageOnID;
            }
        }

        /// <summary>
        /// NoteOff message for this note's MIDI channel
        /// </summary>
        public override int MessageOffID
        {
            get
            {
                return m_messageOffID;
            }
        }

        #endregion

        #region MIDI List Properties

        #pragma warning disable 1591

        public static VstMidiEvent MetronomeQuarterOnEvent = new VstMidiEvent(0, 0, 0, m_metronomeQuarterOnData, 0, 100);
        public static VstMidiEvent MetronomeMeasureOnEvent = new VstMidiEvent(0, 0, 0, m_metronomeMeasureOnData, 0, 100); 
        public static VstMidiEvent MetronomeOffEvent = new VstMidiEvent(0, 0, 0, m_metronomeOffData, 0, 100);

        public static VstMidiEvent CrashOnEvent = new VstMidiEvent(0, 0, 0, m_crashOnData, 0, 100);
        public static VstMidiEvent CrashOffEvent = new VstMidiEvent(0, 0, 0, m_crashOffData, 0, 100);

        //Panic button events
        public static VstMidiEvent BassOffEvent = new VstMidiEvent(0, 0, 0, m_bassNotesOff, 0, 100);
        public static VstMidiEvent MelodyOffEvent = new VstMidiEvent(0, 0, 0, m_melodyNotesOff, 0, 100);
        public static VstMidiEvent RhythmOffEvent = new VstMidiEvent(0, 0, 0, m_rhythmNotesOff, 0, 100);

        //Tempo controls
        public static VstMidiEvent TempoUpEvent = new VstMidiEvent(0, 0, 0, m_inceaseTempo, 0, 100);
        public static VstMidiEvent TempoDownEvent = new VstMidiEvent(0, 0, 0, m_decreaseTempo, 0, 100);

        //Scroll events
        public static VstMidiEvent ScrollUpEvent = new VstMidiEvent(0, 0, 0, m_scrollUp, 0, 100);
        public static VstMidiEvent ScrollDownEvent = new VstMidiEvent(0, 0, 0, m_scrollDown, 0, 100);

        #pragma warning restore 1591

        #endregion

        #region Comparison

        /// <summary>
        /// Comparison method for the sort
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override int CompareTo(object obj)
        {
            if (obj == null) return 1;

            ConcreteNote otherNote = obj as ConcreteNote;

            if (otherNote != null)
            {
                if (this.m_measure != otherNote.m_measure)
                    return this.m_measure.CompareTo(otherNote.m_measure);
                else
                    return this.m_location.CompareTo(otherNote.m_location);
            }
            else
                throw new ArgumentException("Object is not a note");
        }

        #endregion

        #region Methods

        /// <summary>
        /// Determine the duration of this note
        /// </summary>
        protected override void CalculateDuration()
        {
            int length;

            if (m_measure == m_offMeasure)
                length = m_offLocation - m_location;
            else
                length = (m_phrase.PlayableNotesInMeasure - m_location) + m_offLocation;

            switch (Resolution)
            {
                case 1:
                    switch (length)
                    {
                        case 2:
                            m_noteDuration = Duration.Half;
                            break;
                        case 3:
                            m_noteDuration = Duration.DottedHalf;
                            break;
                        case 4:
                            m_noteDuration = Duration.Whole;
                            break;
                        default:
                            m_noteDuration = Duration.Quarter;
                            break;

                    }
                    break;
                case 2:
                    switch (length)
                    {
                        case 2:
                            m_noteDuration = Duration.Quarter;
                            break;
                        case 3:
                            m_noteDuration = Duration.DottedQuarter;
                            break;
                        case 4:
                            m_noteDuration = Duration.Half;
                            break;
                        case 5:
                            m_noteDuration = Duration.HalfBoundEigth;
                            break;
                        case 6:
                            m_noteDuration = Duration.DottedHalf;
                            break;
                        case 7:
                            m_noteDuration = Duration.HalfBoundDottedQuater;
                            break;
                        case 8:
                            m_noteDuration = Duration.Whole;
                            break;
                        default:
                            m_noteDuration = Duration.Eigth;
                            break;
                    }
                    break;
                case 4:
                    switch (length)
                    {
                        case 2:
                            m_noteDuration = Duration.Eigth;
                            break;
                        case 3:
                            m_noteDuration = Duration.DottedEigth;
                            break;
                        case 4:
                            m_noteDuration = Duration.Quarter;
                            break;
                        case 5:
                            m_noteDuration = Duration.QuarterBoundSixteenth;
                            break;
                        case 6:
                            m_noteDuration = Duration.DottedQuarter;
                            break;
                        case 7:
                            m_noteDuration = Duration.DoubleDottedQuarter;
                            break;
                        case 8:
                            m_noteDuration = Duration.Half;
                            break;
                        case 9:
                            m_noteDuration = Duration.HalfBoundSixteenth;
                            break;
                        case 10:
                            m_noteDuration = Duration.HalfBoundEigth;
                            break;
                        case 11:
                            m_noteDuration = Duration.HalfBoundDottedEigth;
                            break;
                        case 12:
                            m_noteDuration = Duration.DottedHalf;
                            break;
                        case 13:
                            m_noteDuration = Duration.DottedHalfBoundSixteenth;
                            break;
                        case 14:
                            m_noteDuration = Duration.DoubleDottedHalf;
                            break;
                        case 15:
                            m_noteDuration = Duration.DottedHalfBoundDottedEigth;
                            break;
                        case 16:
                            m_noteDuration = Duration.Whole;
                            break;
                        default:
                            m_noteDuration = Duration.Sixteenth;
                            break;
                    }
                    break;
            }
        }

        #endregion
    }
}
